﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Linq;
using Odyssey.Utils;
using System.Collections.Generic;

namespace Odyssey.Effects
{

    public enum ContinuumMode
    {
        Master,
        Detail
    }

    /// <summary>
    /// Class to provide a continuum effect for transitioning between a master and a detail page.
    /// </summary>
    public class ContinuumEffect : PageEffect
    {
        const double slideOffset = 250.0;


        #region attached properties

        public static bool GetSlide(DependencyObject obj)
        {
            return (bool)obj.GetValue(SlideProperty);
        }

        public static void SetSlide(DependencyObject obj, bool value)
        {
            obj.SetValue(SlideProperty, value);
        }

        public static readonly DependencyProperty SlideProperty =
            DependencyProperty.RegisterAttached("Slide", typeof(bool), typeof(ContinuumEffect), new PropertyMetadata(false));

        public static bool GetIsItem(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsItemProperty);
        }

        public static void SetIsItem(DependencyObject obj, bool value)
        {
            obj.SetValue(IsItemProperty, value);
        }

        public static readonly DependencyProperty IsItemProperty =
            DependencyProperty.RegisterAttached("IsItem", typeof(bool), typeof(ContinuumEffect), new PropertyMetadata(false));

        #endregion

        /// <summary>
        /// Gets or sets whether the effect applies to a master or detail page.
        /// </summary>
        public ContinuumMode Mode { get; set; }

        public bool IsItemEnabled { get; set; }

        public double MasterReturnScale { get; set; }

        /// <summary>
        /// Gets or sets the name of the anchestor element which contains the element which is marked with IsElement=true;
        /// </summary>
        public string Scope { get; set; }

        protected override void Initialize()
        {
            base.Initialize();
            MasterReturnScale = 1.1d;
            IsItemEnabled = true;
        }

        public override void Stop()
        {
            //if (Storyboard != null) Storyboard.Stop();
            //base.Stop();
        }

        protected override void Activate(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard)
        {
            switch (Mode)
            {
                case ContinuumMode.Detail: ActivateDetail(page, storyboard); break;
                case ContinuumMode.Master: ActivateMaster(page, storyboard); break;
            }
        }

        protected override void Deactivate(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard, bool isActive)
        {
            switch (Mode)
            {
                case ContinuumMode.Detail: DeactivateDetail(page, storyboard); break;
                case ContinuumMode.Master: DeactivateMaster(page, storyboard); break;
            }
        }

        private void ActivateMaster(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard)
        {
            this.item = null;
            FrameworkElement item = GetItemElement(page);
            var slides = GetSlideElements(page);
            if (item != null)
            {
                bool hasSlide = slides.Any();
                PlaneProjection projection = item.GetPlaneProjection(true);
                {
                    DoubleAnimation timeline = new DoubleAnimation
                    {
                        To = page.ActualWidth * 2,
                        EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseIn }
                    };
                    AddTimeline(timeline, projection, "GlobalOffsetX");

                    timeline = new DoubleAnimation
                    {
                        To = hasSlide ? -130 - 200 : -130,
                        EasingFunction = new BackEase { EasingMode = EasingMode.EaseIn, Amplitude = 1.3 }
                    };
                    AddTimeline(timeline, projection, "GlobalOffsetY");
                }

            }
            foreach (var slide in slides)
            {
                PlaneProjection projection = slide.GetPlaneProjection(true);
                DoubleAnimation timeline = new DoubleAnimation
                {
                    To = 200.0,
                    Duration = this.Duration,
                    EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
                };
                AddTimeline(timeline, projection, "GlobalOffsetY");
            }
            {
                DoubleAnimation timeline = new DoubleAnimation
                {
                    Duration = this.Duration,
                    EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseIn, Exponent = 2 },
                    To = 0.0
                };
                AddTimeline(timeline, page, "Opacity");
            }
        }

        private void DeactivateMaster(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard)
        {
            FrameworkElement item = GetItemElement(page);

            bool isInitialized = this.IsInitialized;

            if (!isInitialized)
            {
                page.Opacity = 0;
            }

            if (item != null)
            {
                PlaneProjection projection = item.GetPlaneProjection(true);
                {
                    TimeSpan duration = TimeSpan.FromSeconds(this.Duration.TotalSeconds * MasterReturnScale);
                    DoubleAnimation timeline = new DoubleAnimation
                    {
                        From = -page.ActualWidth,
                        To = 0.0,
                        EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
                    };
                    AddTimeline(timeline, projection, "GlobalOffsetX", duration);

                    timeline = new DoubleAnimation
                    {
                        From = -200.0,
                        To = 0.0,
                        EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut }
                    };
                    AddTimeline(timeline, projection, "GlobalOffsetY", duration);
                }

            }
            AddSlideDeactivatedTimeline(page);
            {
                DoubleAnimation timeline = new DoubleAnimation
                {
                    Duration = this.Duration,
                    EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut, Exponent = 4 },
                    From = 0.0,
                    To = 1.0
                };
                AddTimeline(timeline, page, "Opacity");
            }
        }



        private void ActivateDetail(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard)
        {
            FrameworkElement item = GetItemElement(page);

            if (item != null)
            {
                PlaneProjection projection = item.GetPlaneProjection(true);

                Timeline timeline = new DoubleAnimation
                {
                    To = 100.0,
                    EasingFunction = new PowerEase { Power = 3.5 }

                };

                AddTimeline(timeline, projection, "GlobalOffsetX");

                var gt = page.TransformToVisual(item);
                Point pt;
                gt.TryTransform(new Point(), out pt);
                double top = pt.Y - item.ActualHeight - 160d;

                timeline = new DoubleAnimation
                {
                    To = top,
                    EasingFunction = new PowerEase { Power = 1.5, EasingMode=EasingMode.EaseIn }
                };
                AddTimeline(timeline, projection, "GlobalOffsetY");
            }
            AddSlideActivatedTimeline(page);
            //page.Opacity = 1.0;
            AddTimeline(new DoubleAnimation
            {
                To = 0.0,
                EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseIn, Exponent = 10 }
            }, page, "Opacity");
        }


        private void DeactivateDetail(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard)
        {
            FrameworkElement item = GetItemElement(page);

            if (!IsInitialized)
            {
                page.Opacity = 0.0;
            }

            if (item != null)
            {
                PlaneProjection projection = item.GetPlaneProjection(true);

                Timeline timeline = new DoubleAnimation
                {
                    From = 800,
                    To = 0.0,
                    EasingFunction = new PowerEase { Power = 1.6 }

                };

                AddTimeline(timeline, projection, "GlobalOffsetX");

                timeline = new DoubleAnimation
                {
                    From = 120.0,
                    To = 0.0,
                    EasingFunction = new BackEase { Amplitude = 1.5 }
                };
                AddTimeline(timeline, projection, "GlobalOffsetY");
            }
            AddSlideDeactivatedTimeline(page);


            AddTimeline(new DoubleAnimation
            {
                To = 1.0,
                EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseOut, Exponent = 10.0 }
            }, page, "Opacity");
        }

        private void AddSlideDeactivatedTimeline(Microsoft.Phone.Controls.PhoneApplicationPage page, double offset = slideOffset)
        {
            var slides = GetSlideElements(page);
            foreach (var slide in slides)
            {
                PlaneProjection projection = slide.GetPlaneProjection(true);
                DoubleAnimation timeline = new DoubleAnimation
                {
                    From = offset,
                    To = 0.0,
                    Duration = this.Duration,
                    EasingFunction = new QuinticEase { EasingMode = EasingMode.EaseOut }
                };
                AddTimeline(timeline, projection, "GlobalOffsetY");
            }
        }

        private void AddSlideActivatedTimeline(Microsoft.Phone.Controls.PhoneApplicationPage page)
        {
            foreach (var slide in GetSlideElements(page))
            {
                PlaneProjection projection = slide.GetPlaneProjection(true);
                DoubleAnimation timeline = new DoubleAnimation
                {
                    To = slideOffset,
                    Duration = this.Duration,
                    EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
                };
                AddTimeline(timeline, projection, "GlobalOffsetY");
            }
        }

        private FrameworkElement item;


        private FrameworkElement GetItemElement(PhoneApplicationPage page)
        {
            if (!IsItemEnabled) return null;
            if (item == null)
            {
                FrameworkElement container = null;
                if (!string.IsNullOrEmpty(Scope))
                {
                    container = page.FindName(Scope) as FrameworkElement;
                }
                if (container == null) container = page;
                item = container.GetVisualDescendants().FirstOrDefault(e => GetIsItem(e)) as FrameworkElement;
            }
            return item;
        }

        private IEnumerable<FrameworkElement> GetSlideElements(PhoneApplicationPage page)
        {
            return page.GetVisualDescendants().Where(e => GetSlide(e)).OfType<FrameworkElement>();
        }


    }
}
